import cast from '@ohos.cast'
import Log from '../../../../../../../common/src/main/ets/default/Log';
import createOrGet from '../../../../../../../common/src/main/ets/default/SingleInstanceHelper';
import { MiracastErrorType, MiracastServiceListener } from '../common/Constants';

const TAG = 'MiracastModel';

const MIRACAST_PROTOCOL_TYPE = 8;//we use the fixed value here as cast.ts is a bit different from framework

enum MiracastAsyncStatus{
  IDLE,
  PREPARING,
  WORKING
}

enum MiracastStage{
  IDLE, //not start yet
  START_DISCOVERING, //call startDiscovering() but not ret yet
  DISCOVERING,//call startDiscovering() succeed
  CREATE_SESSION,//call createSession() but not ret yet
  SESSION_CREATED, //call createSession() succeed
  START_ADDING_DEVICE, //call addDevice() but not ret yet
  WAITING_CAST_RESULT, //wait deviceState event
  CONNECTING_DEVICE,
  DEVICE_CONNECTED,
  CAST_SUCCEED, //cast succeed
}



function isOk(error:Error):boolean{
  return error == undefined;
}
class MiracastService {
  private mIsStart = false;
  private mIsDiscovering = false;//is discovering devices
  private mMiracastStage: MiracastStage = MiracastStage.IDLE;

  private mSession : cast.CastSession = undefined;
  private mDeviceId : string  = undefined;// target device mac

  mListener: MiracastServiceListener;

  constructor() {
  }

  setAudioPlayer(devType){
    if(this.mSession){
      if(devType == 0){
        Log.showInfo(TAG, 'cast-> CastSession.setAudioOutput(cast.EndType.CAST_SOURCE).')
        this.mSession.setAudioOutput(cast.EndType.CAST_SOURCE).then(()=>{
          Log.showInfo(TAG, 'cast-> CastSession.setAudioOutput callback succ.')
          AppStorage.setOrCreate('slMiracastUseSinkAudioPlayer', false);
        }).catch((error: Error) => {
          Log.showInfo(TAG, 'cast-> CastSession.setAudioOutput callback failed: ' + error)
        })
      }
      else{
        Log.showInfo(TAG, 'cast-> CastSession.setAudioOutput(cast.EndType.CAST_SINK).')
        this.mSession.setAudioOutput(cast.EndType.CAST_SINK).then(()=>{
          Log.showInfo(TAG, 'cast-> CastSession.setAudioOutput callback succ.')
          AppStorage.setOrCreate('slMiracastUseSinkAudioPlayer', true);
        }).catch((error: Error) => {
          Log.showInfo(TAG, 'cast-> CastSession.setAudioOutput callback failed: ' + error)
        })
      }
    }


  }

  registerListener(listener: MiracastServiceListener) {
    this.mListener = listener;
  }

  unregisterListener() {
    this.mListener = undefined;
  }

  startService(): void {
    Log.showInfo(TAG, 'startService');
    if (this.mIsStart) {
      return;
    }
    this.mIsStart = true;
    Log.showInfo(TAG, 'cast-> call on(\'deviceFound\')');
    cast.on('deviceFound', (deviceList: Array<cast.CastRemoteDevice>) => {
      Log.showInfo(TAG, 'cast-> on(deviceFound) callback')
      if(this.mMiracastStage == MiracastStage.DISCOVERING)
      {
        Log.showDebug(TAG, 'update list')
        this.mListener?.updateDiscoveringItems(deviceList);
      }
    })
  }
  isDiscovering()
  {
    Log.showDebug(TAG, `isDiscovering: ${this.mIsDiscovering} && ${this.mMiracastStage}`)
    return this.mIsDiscovering &&
      (this.mMiracastStage == MiracastStage.DISCOVERING || this.mMiracastStage == MiracastStage.START_DISCOVERING)
  }
  startDiscovery(){
    Log.showInfo(TAG, `startDiscovery(current status:${this.mIsDiscovering}, ${this.mMiracastStage})`)
    if(this.mIsDiscovering == false && this.mMiracastStage == MiracastStage.IDLE)
    {
      Log.showInfo(TAG, 'cast-> call cast.startDiscovery');
      this.mMiracastStage = MiracastStage.START_DISCOVERING;
      cast.startDiscovery([MIRACAST_PROTOCOL_TYPE], (error: Error)=>{
        if(isOk(error)){
          Log.showDebug(TAG, "cast-> discovery callback succeed");
          this.mIsDiscovering = true;
          this.mMiracastStage = MiracastStage.DISCOVERING
          this.mListener.showDiscoveringDialog();
        }
        else{
          Log.showError(TAG, "cast-> discovery callback failed: " + error)
          this.mIsDiscovering = false;
          this.mMiracastStage = MiracastStage.IDLE;
          this.mListener.connectFailed(MiracastErrorType.ERR_START_DISCOVERY, error)
        }
      })
    }
    else{
      Log.showError(TAG, "service's status does not support current operation")
      //Shall we add a dialog tips here?
    }
  }

  connectToTarget(remoteDevice: cast.CastRemoteDevice){
    Log.showInfo(TAG, `connectToTarget(current status:${this.mIsDiscovering}, ${this.mMiracastStage})`)
    if(this.mIsDiscovering == true && this.mMiracastStage == MiracastStage.DISCOVERING) //没有在连接中
    {
      this.mListener?.showConnectingDialog(remoteDevice.deviceName);
      this.mMiracastStage = MiracastStage.CREATE_SESSION;
      cast.createCastSession(
        { protocolType: MIRACAST_PROTOCOL_TYPE, endType: cast.EndType.CAST_SOURCE },
        (error: Error, session: cast.CastSession) => {
          if(isOk(error) == false){
            Log.showError(TAG, "cast-> createCastSession callback failed:" + error);
            this.mMiracastStage = MiracastStage.DISCOVERING;//back status
            this.resetStatus();
            this.mListener?.connectFailed(MiracastErrorType.ERR_CREATE_SESSION, error);
            return;
          }
          Log.showInfo(TAG, "cast-> createCastSession callback succeed.")
          this.mSession = session;
          this.mMiracastStage = MiracastStage.SESSION_CREATED;
          Log.showDebug(TAG, "cast-> call on(event)")
          this.mSession.on('event',  (eventId: cast.EventId, eventInfo: string) =>{
            Log.showInfo(TAG, "cast-> event callback: eventId(" + eventId + "), info(" + eventInfo + ").")
          })
          Log.showDebug(TAG, "cast-> call on(deviceState)")
          this.mSession.on('deviceState', (stateEvent: cast.DeviceStateInfo) => {
            Log.showInfo(TAG,
              `cast-> deviceState callback: eventId("${stateEvent.deviceState}},${stateEvent.deviceId} ")`)
            Log.showInfo(TAG, "cast-> deviceState callback: eventId(" + stateEvent.deviceState + ")")
            if( this.mMiracastStage != MiracastStage.START_ADDING_DEVICE
              && this.mMiracastStage != MiracastStage.WAITING_CAST_RESULT
              && this.mMiracastStage !=  MiracastStage.CONNECTING_DEVICE
              && this.mMiracastStage != MiracastStage.DEVICE_CONNECTED
              && this.mMiracastStage != MiracastStage.CAST_SUCCEED)
            {
              Log.showError(TAG, "service's status(" + this.mMiracastStage
                + ") does not support current operation")
              return;
            }
            if(stateEvent.deviceState == cast.DeviceState.CONNECTING)
            {
              this.mDeviceId = stateEvent.deviceId;
              this.mMiracastStage = MiracastStage.CONNECTING_DEVICE;
            }
            else if (stateEvent.deviceState == cast.DeviceState.CONNECTED){
              this.mDeviceId = stateEvent.deviceId;
              this.mMiracastStage = MiracastStage.DEVICE_CONNECTED;
            }
            else if (stateEvent.deviceState == cast.DeviceState.PLAYING){
              this.mDeviceId = stateEvent.deviceId;
              this.mMiracastStage = MiracastStage.CAST_SUCCEED;
              this.mListener.connectSucceed();
            }
            else if(stateEvent.deviceState == cast.DeviceState.DISCONNECTED)
            {
              this.mDeviceId = undefined
              this.mMiracastStage = MiracastStage.SESSION_CREATED; //no need to removeDevice
              this.resetStatus();
              this.mListener?.finished();
            }

          });

          this.mMiracastStage = MiracastStage.START_ADDING_DEVICE;
          Log.showDebug(TAG, "cast-> call addDevice")
          this.mSession.addDevice(remoteDevice, (error: Error)=>{
            if(isOk(error))
            {
              Log.showInfo(TAG, "cast-> addDevice " + ", callback succeed");
              this.stopDiscovery();
              this.mMiracastStage = MiracastStage.WAITING_CAST_RESULT;
            }
            else{
              Log.showError(TAG, "cast-> addDevice " + ", callback failed: " + error);
              this.mMiracastStage = MiracastStage.SESSION_CREATED //back status
              this.resetStatus();
              this.mListener.connectFailed(MiracastErrorType.ERR_ADD_DEVICE, error)
            }
          })
        }
      )
    }
    else
    {
      Log.showError(TAG, "service's status does not support current operation")
      //Shall we add a dialog tips here?
    }
  }

  private async stopCasting(){
    Log.showInfo(TAG, `stopCasting(current status:${this.mIsDiscovering}, ${this.mMiracastStage})`)
    if(this.mSession && (this.mMiracastStage == MiracastStage.CONNECTING_DEVICE
      || this.mMiracastStage == MiracastStage.DEVICE_CONNECTED
      || this.mMiracastStage == MiracastStage.CAST_SUCCEED) && this.mDeviceId){
      // this.mSession.removeDevice(this.mDeviceId, (error: Error) => {
      //   Log.showInfo(TAG, 'cast-> CastSession.removeDevice callback: ' + error)
      //   this.mMiracastStage = MiracastStage.SESSION_CREATED;//back stage
      //   this.mDeviceId = undefined;
      //
      // })
      Log.showDebug(TAG, "cast-> call CastSession.removeDevice('" + this.mDeviceId + "')")
      await this.mSession.removeDevice(this.mDeviceId).then(()=>{
        Log.showInfo(TAG, 'cast-> CastSession.removeDevice callback succ.')

      }).catch((error: Error) => {
        Log.showInfo(TAG, 'cast-> CastSession.removeDevice callback failed: ' + error)
        /*there is possible failure here, but there is no way to deal with this case.Just drop it.
          use code as below
          if(isOk(error)) { } else {}*/
      })
      this.mMiracastStage = MiracastStage.SESSION_CREATED;//back stage
      this.mDeviceId = undefined;
    }
  }
  private async releaseSession()
  {
    Log.showInfo(TAG, `releaseSession(current status:${this.mIsDiscovering}, ${this.mMiracastStage})`)
    if(this.mSession &&
      (this.mMiracastStage == MiracastStage.SESSION_CREATED
        || this.mMiracastStage == MiracastStage.START_ADDING_DEVICE
        || this.mMiracastStage == MiracastStage.WAITING_CAST_RESULT)){
      Log.showDebug(TAG, "cast-> call CastSession.off('deviceState'")
      this.mSession.off('deviceState')
      // this.mSession.off('deviceState', (stateEvent: cast.DeviceStateInfo) => {
      //   console.info('cast-> off(deviceState) callback: ' + stateEvent)
      // });
      Log.showDebug(TAG, "cast-> call CastSession.off('event'")
      this.mSession.off('event')
      // this.mSession.off('event', (eventId: cast.EventId, eventInfo: string) => {
      //   console.info('cast-> off(event) callback: event(' + eventId + "), eventInfo(" + eventInfo + ").")
      // })
      Log.showDebug(TAG, "cast-> call CastSession.release")
      await this.mSession.release().then(()=>{
        Log.showInfo(TAG, 'cast-> CastSession.release callback succ.')
      }).catch((error: Error)=>{
        Log.showInfo(TAG, 'cast-> CastSession.release callback: ' + error)
        /*there is possible failure here, but there is no way to deal with this case.Just drop it.
          use code as below
          if(isOk(error)) { } else {}*/
      })
      if(this.mIsDiscovering)
        this.mMiracastStage = MiracastStage.DISCOVERING;
      else
        this.mMiracastStage = MiracastStage.IDLE;
      this.mSession = undefined;
    }
  }
  private async stopDiscovery(){
    Log.showInfo(TAG, `stopDiscovery(current status:${this.mIsDiscovering}, ${this.mMiracastStage})`)
    if(this.mIsDiscovering)
    {
      await cast.stopDiscovery().then(()=>{
        Log.showInfo(TAG, "cast-> stopDiscovery callback succeed.");
      }).catch((error: Error)=>{
        Log.showInfo(TAG, "cast-> stopDiscovery callback failed:" + error);
        /*there is possible failure here, but there is no way to deal with this case.Just drop it.
        use code as below
        if(isOk(error)) { } else {}*/
      })
      this.mIsDiscovering = false;
      if(this.mMiracastStage == MiracastStage.DISCOVERING || this.mMiracastStage == MiracastStage.START_DISCOVERING)
        this.mMiracastStage = MiracastStage.IDLE;
    }
  }

  async resetStatus(){
    await this.stopDiscovery();
    await this.stopCasting();
    await this.releaseSession();
  }





  stopService(): void {
    this.resetStatus();
    cast.off('deviceFound',  (deviceList: Array<cast.CastRemoteDevice>) => {
      Log.showInfo(TAG, "cast-> off(deviceFound)")
    })
  }
}

const sMiracastService = createOrGet(MiracastService, TAG);

export default sMiracastService;